package aorm

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"gitee.com/KotlinToGo/aorm/dialect"
	"gitee.com/KotlinToGo/sqlx"
	"reflect"
	"strings"
	"time"
)

// Session 真正操作数据库底层模块, 所有的操作, 最终都会走到这里来获取或修改数据。(Session 的核心功能是与数据库进行交互)
type Session struct {
	IEngine                // 持有一个 IEngine 接口实例，主要是为了方便使用 IEngine 中的两个方法 (即: GetMasterDB()、GetSlaveDB())
	dialect      dialect.Dialect // 各个数据库差异的部分，为适配不同的数据库，映射数据类型和特定的 SQL 语句，创建 dialect 层屏蔽数据库差异。
	table        *Schema         // 利用反射(reflect)完成结构体和数据库表结构的映射，包括表名、字段名、字段类型、字段 tag 等。(主要是为了 Model 映射到 Table)
	clause       Clause          // 构造 SQL 字句，例如: where、limit、insert、select...
	logger       ILogger         // 日志
	master       *sqlx.DB        // 主数据库
	slave        *sqlx.DB        // 从数据库
	tx           *sqlx.Tx        // 事务
	ctx          context.Context
	builder      strings.Builder // 拼接SQL语句
	lastInsertId int64           // 自增主键
	sqlLogs      []string
	lastSql      []interface{} // 被 sql.Exec执行的 sql语句 例如: "insert into users(name,age) VALUES(?,?),(?,?),(?,?)","fizz", 18, "rose", 19, "fizz", 20。
	union        interface{}
	transaction  bool // 是否开启事务，true开启事务，false禁用事务
	err          error
}

var _ISession = (*Session)(nil)

// newSession 参数为 <IEngine>，主要是为了方便将 Engine 的一些值传递给 Session。
func initSession(e IEngine) *Session {
	s := new(Session)
	s.IEngine = e
	s.master = e.GetMasterDB()
	s.slave = e.GetSlaveDB()
	return s
}

func (s *Session) Close() {
	s.master.Close()
	s.slave.Close()
}

// SetTX 设置事务的状态，true:开启事务，false:禁用事务
func (s *Session) SetTX(b bool) {
	s.transaction = b
}

// Begin 开启事务，如果 master节点为nil，则通过slave节点开启事务。
func (s *Session) Begin() (err error) {
	if s.master != nil {
		s.tx, err = s.master.BeginX()
	}else {
		s.tx, err = s.slave.BeginX()
	}
	s.SetTX(true)
	return
}

// Rollback 事务回滚
func (s *Session) Rollback() (err error) {
	err = s.tx.Rollback()
	s.tx = nil
	s.SetTX(false)
	return
}

// Commit 提交事务
func (s *Session) Commit() (err error) {
	err = s.tx.Commit()
	s.tx = nil
	s.SetTX(false)
	return
}


// Raw 拼接 SQL语句，将 sql 和 value 保存到 lastSql
func (s *Session) Raw(sql string,value...interface{}) *Session {
	s.builder.WriteString(sql)
	s.builder.WriteString(" ")
	s.lastSql = append(s.lastSql,value)
	return s
}

// Clear 清空SQL语句
func (s Session) Clear() {
	s.builder.Reset()
	s.lastSql = nil
}

// LastSql 输出执行完的SQL语句
func (s *Session) LastSql() string {
	var sqlStr string
	for _, v := range s.lastSql{
		sqlStr = v.(string)
	}
	return sqlStr
}

// Model 输入 Model 并创建表。参数<value>只能是 struct/*struct。
func (s *Session) Model(value interface{}) *Session {
	if s.table == nil || reflect.TypeOf(value) != reflect.TypeOf(s.table.Model) && s.table.Name != value.(string){
		s.table = new(Schema).Parse(value, s.dialect)
		var columns []string
		for _, field := range s.table.Fields {
			columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag))
		}
		desc := strings.Join(columns, ",")
		_, err := s.Exec(fmt.Sprintf("CREATE TABLE %s (%s);", s.table.Name, desc))
		s.logger.Error(err.Error())
	}
	s.Create()
	return s
}

func (s *Session) RefTable() *Schema {
	if s.table == nil {
		s.logger.Error("Model is not set")
	}
	return s.table
}

// CreateTable create a table in database with a model
func (s *Session) Create() error {
	table := s.RefTable()
	var columns []string
	for _, field := range table.Fields {
		columns = append(columns, fmt.Sprintf("%s %s %s", field.Name, field.Type, field.Tag))
	}

	desc := strings.Join(columns, ",")
	_, err := s.Exec(fmt.Sprintf("CREATE TABLE %s (%s);", table.Name, desc))
	return err
}

// DropTable drops a table with the name of model
func (s *Session) Drop() error {
	_, err := s.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", s.RefTable().Name))
	return err
}

// HasTable returns true of the table exists
func (s *Session) HasTable() bool {
	sql, values := s.dialect.TableExistSQL(s.RefTable().Name)
	row ,_ := s.Query(sql, values...)
	var tmp string
	_ = row.Scan(&tmp)
	return tmp == s.RefTable().Name
}


// Query 原生查询语句。slave(从库)用来做查询操作。
func (s *Session) Query(sqlStr string, values ...interface{}) (rows *sqlx.Rows, err error) {
	// 记录开始时间
	start := time.Now()
	if s.err != nil {
		s.logger.Error(err.Error())
		err = s.err
	}
	// 记录sqlLog
	s.Raw(sqlStr,values)

	var stmt *sqlx.Stmt
	if s.ctx != nil {
		stmt, err = s.master.PrepareXContext(s.ctx, sqlStr)
		rows, err = stmt.QueryXContext(s.ctx,values...)
	}
	// 如果不是事务则从从库中查询。
	if s.tx == nil {
		stmt, err = s.slave.PrepareX(sqlStr)
	} else {
		stmt, err = s.tx.PrepareX(sqlStr)
	}

	if err != nil {
		s.logger.Error(err.Error())
		return
	}

	defer stmt.Close()
	rows, err = stmt.QueryX(values...) //使用给定的参数执行准备好的查询语句，并将查询结果作为行返回。
	if err != nil {
		s.logger.Error(err.Error())
		return
	}
	defer rows.Close()
	//err = s.scan(rows) // 绑定结果集
	if err != nil {
		s.logger.Error(err.Error())
		return
	}

	timeDuration := time.Since(start)
	s.logger.Slow(s.LastSql(), timeDuration)
	s.logger.Sql(s.LastSql(), timeDuration)
	//result = s.GetIBinder().GetBindAll()
	return rows,err
}

func (s *Session) QueryRow(sqlStr string, values ...interface{}) (row *sqlx.Row, err error) {
	// 记录开始时间
	start := time.Now()
	if s.err != nil {
		s.logger.Error(err.Error())
		err = s.err
	}
	// 记录sqlLog
	s.Raw(sqlStr,values)

	var stmt *sqlx.Stmt
	if s.ctx != nil {
		stmt, err = s.master.PrepareXContext(s.ctx, sqlStr)
	}
	// 如果不是事务则从从库中查询。
	if s.tx == nil {
		stmt, err = s.slave.PrepareX(sqlStr)
	} else {
		stmt, err = s.tx.PrepareX(sqlStr)
	}

	if err != nil {
		s.logger.Error(err.Error())
		return
	}
	defer stmt.Close()
	row = stmt.QueryRowX(values...) //使用给定的参数执行准备好的查询语句，并将查询结果作为行返回。
	if err != nil {
		s.logger.Error(err.Error())
		return
	}

	//err = s.scan(rows) // 绑定结果集
	if err != nil {
		s.logger.Error(err.Error())
		return
	}

	timeDuration := time.Since(start)
	s.logger.Slow(s.LastSql(), timeDuration)
	s.logger.Sql(s.LastSql(), timeDuration)
	//result = s.GetIBinder().GetBindAll()
	return row,err
}

// Exec 原生增、删、改语句，master(主库)用来做 增、删、改操作。
func (s *Session) Exec(sqlStr string, values ...interface{}) (result sql.Result, err error) {
	// 记录开始时间
	start := time.Now()

	if s.err != nil {
		s.logger.Error(err.Error())
		return
	}

	// 记录sqlLog
	s.Raw(sqlStr,values)

	var operationType = strings.ToLower(sqlStr[0:6])
	if operationType == "select" {
		s.logger.Error(err.Error())
		err = errors.New(" Execute does not allow select operations, please use Query")
		return
	}

	var stmt *sqlx.Stmt
	if s.ctx != nil {
		stmt, err = s.master.PrepareXContext(s.ctx, sqlStr)
		result, err = stmt.ExecContext(s.ctx,values...)
	}
	if s.tx == nil {
		stmt, err = s.master.PrepareX(sqlStr)
	} else {
		stmt, err = s.tx.PrepareX(sqlStr)
	}

	if err != nil {
		s.logger.Error(err.Error())
		return
	}

	defer stmt.Close()
	result, err = stmt.Exec(values...)
	if err != nil {
		s.logger.Error(err.Error())
		return
	}

	if operationType == "insert" {
		// get last insert id
		lastInsertId, err := result.LastInsertId()
		if err == nil {
			s.lastInsertId = lastInsertId
		} else {
			s.logger.Error(err.Error())
		}
	}
	// get rows affected
	//rowsAffected, err = result.RowsAffected()
	timeDuration := time.Since(start)
	if timeDuration.Seconds() > 1 {
		s.logger.Slow(s.LastSql(), timeDuration)
	} else {
		s.logger.Sql(s.LastSql(), timeDuration)
	}
	return result,err
}

// Where <desc>是具体的SQL语句，例如: "id=? and name=?"，<args>是 ? 这个占位符的具体值，例如: 2,小明...
func (s *Session) Where(desc string, args ...interface{}) ISession {
	var vars []interface{}
	s.clause.Set(Where, append(append(vars, desc), args...)...)
	return s
}

// Limit adds limit condition to clause
func (s *Session) Limit(num int) ISession {
	s.clause.Set(Limit, num)
	return s
}

// OrderBy adds order by condition to clause
func (s *Session) Order(desc string) ISession {
	s.clause.Set(OrderBy, desc)
	return s
}








